package jenkins.python.pwm; import java.io.File; import java.nio.charset.Charset; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; import java.io.IOException; import java.util.List; import java.util.LinkedList; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SimpleName; public abstract class AbstractTypeDeclFinder { private final String CHARSET = "UTF-8"; private final File sourceCodeDir; private List<List<TypeDeclaration>> wantedTypes; public AbstractTypeDeclFinder() { sourceCodeDir = Application.getSrcDir(); } /** * Returns a list of lists of wanted type declarations and all parent type declarations. */ public List<List<TypeDeclaration>> getAllDeclarations() throws JavaParserException { wantedTypes = new LinkedList<List<TypeDeclaration>>(); searchDir(sourceCodeDir); return wantedTypes; } /** * Recursively search in the given directory for wanted type declarations. */ private void searchDir(File dir) throws JavaParserException { File[] files = dir.listFiles(); if (files == null) { String errStr = "error while scanning directory " + dir.getPath() + " occured"; throw new JavaParserException(errStr); } for (int i = 0; i < files.length; i++) { File f = files[i]; if (f.isDirectory()) { searchDir(f); } else if (f.getName().endsWith(".java")) { Logger.verbose("parsing file " + f.getPath()); searchInFile(f); } else { Logger.verbose("file " + f.getName() + " ignored"); } } } /** * Returns the file as a char array. */ private char[] readFile(File f) throws IOException { Charset charset = Charset.forName(CHARSET); byte[] encoded = Files.readAllBytes(Paths.get(f.getPath())); return charset.decode(ByteBuffer.wrap(encoded)).toString().toCharArray(); } /** * Parses a given file and returns a CompilationUnit object as a root node. */ private CompilationUnit parseFile(File f) throws JavaParserException { ASTParser parser = ASTParser.newParser(AST.JLS4); try { parser.setSource(readFile(f)); } catch (IOException e) { String errStr = "cannot parse file " + f.getPath() + " caused by " + e.getMessage(); throw new JavaParserException(errStr); } parser.setKind(ASTParser.K_COMPILATION_UNIT); CompilationUnit cu = (CompilationUnit)parser.createAST(null); return cu; } /** * Recursively search for the parent TypeDeclarations and adds them to the list. */ private void searchForParents(List<TypeDeclaration> list) throws JavaParserException { TypeDeclaration lastDecl = list.get(list.size()-1); Type superClass = lastDecl.getSuperclassType(); if (superClass == null) { // the last type declaration inherits from the Object class return; } String fullName = NameResolver.getFullName(superClass); if (!fullName.startsWith("jenkins") && !fullName.startsWith("hudson")) { // the last type declaration inherits from the java.* or 3rd party class Logger.verbose("skipping parent class: " + fullName); return; } File path = PathResolver.getPath(fullName); String[] nameParts = fullName.split("\\."); String shortName = nameParts[nameParts.length-1]; CompilationUnit cu = parseFile(path); ParentTypeVisitor visitor = new ParentTypeVisitor(shortName); cu.accept(visitor); if (visitor.getFound()) { TypeDeclaration parent = visitor.getTypeDecl(); Logger.verbose("parent: " + NameResolver.getFullName(parent)); list.add(parent); searchForParents(list); } else { Logger.error("cannot find a type declaration: " + fullName); } } /** * Search in the given file for the wanted type declaration. */ private void searchInFile(File f) throws JavaParserException { CompilationUnit cu = parseFile(f); WantedTypeVisitor visitor = new WantedTypeVisitor(); cu.accept(visitor); if (visitor.getFound()) { String typeName = NameResolver.getFullName(visitor.getTypeDecl()); Logger.verbose("wanted type declaration " + typeName + " found"); List<TypeDeclaration> list = new LinkedList<TypeDeclaration>(); // add the found type declaration to the list list.add(visitor.getTypeDecl()); searchForParents(list); Logger.verbose("number of parent classes: " + new Integer(list.size()-1)); // pass on parametric types passOnTypes(list); // add the list (of the type declaration and its parents) to the wanted types list wantedTypes.add(list); } } protected boolean isAbstract(TypeDeclaration td) { if (Modifier.isAbstract(td.getModifiers())) { return true; } else { return false; } } /** * Replaces a name of the parametric type in the superclass. */ protected void passOnTypes(List<TypeDeclaration> list) { // this list has no superclasses if (list.size() < 2) { return; } Type superType = list.get(0).getSuperclassType(); if (!superType.isParameterizedType()) { return; } ParameterizedType superT = (ParameterizedType)superType; TypeDeclaration parentTD = list.get(1); for (int i = 0; i < superT.typeArguments().size(); i++) { // get a new type Type newType = (Type)superT.typeArguments().get(i); // get an old type SimpleName oldTypeName = ((TypeParameter)parentTD.typeParameters().get(i)).getName(); String oldType = oldTypeName.getFullyQualifiedName(); // replace the old type with a new type ReplaceTypeVisitor visitor = new ReplaceTypeVisitor(newType, oldType); parentTD.accept(visitor); } } /** * Determines if a type declaration is wanted by a concrete finder. */ protected abstract boolean isWanted(TypeDeclaration typeDecl); /** * Searches for old type occurrences and replaces them with a new type. */ private class ReplaceTypeVisitor extends ASTVisitor { private Type newType; private String oldType; ReplaceTypeVisitor(Type aNewType, String anOldType) { newType = aNewType; oldType = anOldType; } private Type replaceType(SimpleType type) { AST ast = type.getAST(); if (type.getName().getFullyQualifiedName().equals(oldType)) { return (Type)ASTNode.copySubtree(ast, newType); } else { return (Type)ASTNode.copySubtree(ast, type); } } private Type replaceType(ParameterizedType type) { AST ast = type.getAST(); Type basicType = type.getType(); if (basicType.isSimpleType()) { type.setType(replaceType((SimpleType)basicType)); } else if (basicType.isParameterizedType()) { type.setType(replaceType((ParameterizedType)basicType)); } for (int i = 0; i < type.typeArguments().size(); i++) { Type paramType = (Type)type.typeArguments().get(i); if (paramType.isSimpleType()) { type.typeArguments().set(i, replaceType((SimpleType)paramType)); } else if (paramType.isParameterizedType()) { type.typeArguments().set(i, replaceType((ParameterizedType)paramType)); } } return (Type)ASTNode.copySubtree(ast, type); } public boolean visit(MethodDeclaration node) { Type returnType = node.getReturnType2(); if (returnType != null) { if (returnType.isSimpleType()) { node.setReturnType2(replaceType((SimpleType)returnType)); } else if (returnType.isParameterizedType()) { node.setReturnType2(replaceType((ParameterizedType)returnType)); } } for (int i = 0; i < node.parameters().size(); i++) { SingleVariableDeclaration svd = (SingleVariableDeclaration)node.parameters().get(i); Type varType = svd.getType(); if (varType.isSimpleType()) { svd.setType(replaceType((SimpleType)varType)); } else if (varType.isParameterizedType()) { svd.setType(replaceType((ParameterizedType)varType)); } } return false; } } /** * Visits all TypeDeclaration nodes and checks if they are wanted. * Only the last wanted node in the tree is found!! */ private class WantedTypeVisitor extends ASTVisitor { private boolean found = false; private TypeDeclaration typeDecl; public boolean visit(TypeDeclaration node) { if (isWanted(node)) { found = true; typeDecl = node; } return true; } public boolean getFound() { return found; } public TypeDeclaration getTypeDecl() { return typeDecl; } } /** * Visits all TypeDeclaration nodes and checks if the name equals. */ private class ParentTypeVisitor extends ASTVisitor { private boolean found = false; private TypeDeclaration typeDecl; private String name; ParentTypeVisitor(String aName) { super(); name = aName; } public boolean visit(TypeDeclaration node) { if (node.getName().getFullyQualifiedName().equals(name)) { found = true; typeDecl = node; return false; } return true; } public boolean getFound() { return found; } public TypeDeclaration getTypeDecl() { return typeDecl; } } }